/* eslint-disable */
// 一个命名的符号/标识，用于检测Signal实例，即使它们不是
// 使用相同版本的signals库创建的。
const BRAND_SYMBOL = Symbol.for("preact-signals");

// Computed和Effect的标志位。
const RUNNING = 1 << 0;
const NOTIFIED = 1 << 1;
const OUTDATED = 1 << 2;
const DISPOSED = 1 << 3;
const HAS_ERROR = 1 << 4;
const TRACKING = 1 << 5;

// 一个链表节点，用于跟踪依赖项（sources）和依赖者（targets）。
// 同时用于记录target最后看到的source版本号。
type Node = {
	// 一个target依赖其值的source。
	_source: Signal;
	_prevSource?: Node;
	_nextSource?: Node;

	// 一个依赖于source的target，当source变化时应该被通知。
	_target: Computed | Effect;
	_prevTarget?: Node;
	_nextTarget?: Node;

	// target最后看到的source版本号。我们使用版本号
	// 而不是存储source值，因为source值可能占用任意数量
	// 的内存，并且computed可能会永远保留它们，因为它们是惰性求值的。
	// 使用特殊值-1标记潜在未使用但可回收的节点。
	_version: number;

	// 用于在进入和退出新的评估上下文时记住和回滚source之前的`._node`值。
	_rollbackNode?: Node;
};

function startBatch() {
	batchDepth++;
}

function endBatch() {
	if (batchDepth > 1) {
		batchDepth--;
		return;
	}

	let error: unknown;
	let hasError = false;

	while (batchedEffect !== undefined) {
		let effect: Effect | undefined = batchedEffect;
		batchedEffect = undefined;

		batchIteration++;

		while (effect !== undefined) {
			const next: Effect | undefined = effect._nextBatchedEffect;
			effect._nextBatchedEffect = undefined;
			effect._flags &= ~NOTIFIED;

			if (!(effect._flags & DISPOSED) && needsToRecompute(effect)) {
				try {
					effect._callback();
				} catch (err) {
					if (!hasError) {
						error = err;
						hasError = true;
					}
				}
			}
			effect = next;
		}
	}
	batchIteration = 0;
	batchDepth--;

	if (hasError) {
		throw error;
	}
}

/**
 * 将多个值更新合并为一个在提供的回调结束时的"提交"。
 *
 * 批处理可以嵌套，更改仅在最外层批处理回调
 * 完成后才会刷新。
 *
 * 访问在批处理中已修改的signal将反映其更新后的
 * 值。
 *
 * @param fn 回调函数。
 * @returns 回调函数返回的值。
 */
function batch<T>(fn: () => T): T {
	if (batchDepth > 0) {
		return fn();
	}
	/*@__INLINE__**/ startBatch();
	try {
		return fn();
	} finally {
		endBatch();
	}
}

// 当前正在评估的computed或effect。
let evalContext: Computed | Effect | undefined = undefined;

/**
 * 运行一个可以访问signal值而不订阅signal更新的
 * 回调函数。
 *
 * @param fn 回调函数。
 * @returns 回调函数返回的值。
 */
function untracked<T>(fn: () => T): T {
	const prevContext = evalContext;
	evalContext = undefined;
	try {
		return fn();
	} finally {
		evalContext = prevContext;
	}
}

// 收集到批处理中的effects。
let batchedEffect: Effect | undefined = undefined;
let batchDepth = 0;
let batchIteration = 0;

// signals的全局版本号，用于在全局没有变化时快速处理重复的
// computed.peek()/computed.value调用。
let globalVersion = 0;

function addDependency(signal: Signal): Node | undefined {
	if (evalContext === undefined) {
		return undefined;
	}

	let node = signal._node;
	if (node === undefined || node._target !== evalContext) {
		/**
		 * `signal` is a new dependency. Create a new dependency node, and set it
		 * as the tail of the current context's dependency list. e.g:
		 *
		 * { A <-> B       }
		 *         ↑     ↑
		 *        tail  node (new)
		 *               ↓
		 * { A <-> B <-> C }
		 *               ↑
		 *              tail (evalContext._sources)
		 */
		node = {
			_version: 0,
			_source: signal,
			_prevSource: evalContext._sources,
			_nextSource: undefined,
			_target: evalContext,
			_prevTarget: undefined,
			_nextTarget: undefined,
			_rollbackNode: node,
		};

		if (evalContext._sources !== undefined) {
			evalContext._sources._nextSource = node;
		}
		evalContext._sources = node;
		signal._node = node;

		// Subscribe to change notifications from this dependency if we're in an effect
		// OR evaluating a computed signal that in turn has subscribers.
		if (evalContext._flags & TRACKING) {
			signal._subscribe(node);
		}
		return node;
	} else if (node._version === -1) {
		// `signal` is an existing dependency from a previous evaluation. Reuse it.
		node._version = 0;

		/**
		 * If `node` is not already the current tail of the dependency list (i.e.
		 * there is a next node in the list), then make the `node` the new tail. e.g:
		 *
		 * { A <-> B <-> C <-> D }
		 *         ↑           ↑
		 *        node   ┌─── tail (evalContext._sources)
		 *         └─────│─────┐
		 *               ↓     ↓
		 * { A <-> C <-> D <-> B }
		 *                     ↑
		 *                    tail (evalContext._sources)
		 */
		if (node._nextSource !== undefined) {
			node._nextSource._prevSource = node._prevSource;

			if (node._prevSource !== undefined) {
				node._prevSource._nextSource = node._nextSource;
			}

			node._prevSource = evalContext._sources;
			node._nextSource = undefined;

			evalContext._sources!._nextSource = node;
			evalContext._sources = node;
		}

		// We can assume that the currently evaluated effect / computed signal is already
		// subscribed to change notifications from `signal` if needed.
		return node;
	}
	return undefined;
}

/**
 * 普通signal和computed signal的基类。
 */
//
// 稍后会定义一个同名函数，所以我们需要忽略TypeScript的
// 关于重复声明变量的警告。
//
// 这个类在这里声明，但稍后使用ES5风格的原型实现。
// 这样可以更好地控制转译后的输出大小。
// @ts-ignore: "Cannot redeclare exported variable 'Signal'."
declare class Signal<T = any> {
	/** @internal */
	_value: unknown;

	/**
	 * @internal
	 * 版本号应始终 >= 0，因为特殊值-1被Nodes用来
	 * 表示潜在未使用但可回收的节点。
	 */
	_version: number;

	/** @internal */
	_node?: Node;

	/** @internal */
	_targets?: Node;

	constructor(value?: T, options?: SignalOptions<T>);

	/** @internal */
	_refresh(): boolean;

	/** @internal */
	_subscribe(node: Node): void;

	/** @internal */
	_unsubscribe(node: Node): void;

	/** @internal */
	_watched?(this: Signal<T>): void;

	/** @internal */
	_unwatched?(this: Signal<T>): void;

	subscribe(fn: (value: T) => void): () => void;

	name?: string;

	valueOf(): T;

	toString(): string;

	toJSON(): T;

	peek(): T;

	brand: typeof BRAND_SYMBOL;

	get value(): T;
	set value(value: T);
}

export interface SignalOptions<T = any> {
	watched?: (this: Signal<T>) => void;
	unwatched?: (this: Signal<T>) => void;
	name?: string;
}

export const isSignal = (obj: any): obj is Signal<any> => obj && obj.brand === BRAND_SYMBOL;

/** @internal */
// 已经声明了同名的类，所以我们需要忽略
// TypeScript关于重复声明变量的警告。
//
// 之前声明的类在这里使用ES5风格的原型实现。
// 这样可以更好地控制转译后的输出大小。
// @ts-ignore: "Cannot redeclare exported variable 'Signal'."
function Signal(this: Signal, value?: unknown, options?: SignalOptions) {
	this._value = value;
	this._version = 0;
	this._node = undefined;
	this._targets = undefined;
	this._watched = options?.watched;
	this._unwatched = options?.unwatched;
	this.name = options?.name;
}

Signal.prototype.brand = BRAND_SYMBOL;

Signal.prototype._refresh = function () {
	return true;
};

Signal.prototype._subscribe = function (node) {
	const targets = this._targets;
	if (targets !== node && node._prevTarget === undefined) {
		node._nextTarget = targets;
		this._targets = node;

		if (targets !== undefined) {
			targets._prevTarget = node;
		} else {
			untracked(() => {
				this._watched?.call(this);
			});
		}
	}
};

Signal.prototype._unsubscribe = function (node) {
	// 只有当signal有任何订阅者时才执行取消订阅步骤。
	if (this._targets !== undefined) {
		const prev = node._prevTarget;
		const next = node._nextTarget;
		if (prev !== undefined) {
			prev._nextTarget = next;
			node._prevTarget = undefined;
		}

		if (next !== undefined) {
			next._prevTarget = prev;
			node._nextTarget = undefined;
		}

		if (node === this._targets) {
			this._targets = next;
			if (next === undefined) {
				untracked(() => {
					this._unwatched?.call(this);
				});
			}
		}
	}
};

Signal.prototype.subscribe = function (fn) {
	return effect(
		() => {
			const value = this.value;
			const prevContext = evalContext;
			evalContext = undefined;
			try {
				fn(value);
			} finally {
				evalContext = prevContext;
			}
		},
		{ name: "sub" }
	);
};

Signal.prototype.valueOf = function () {
	return this.value;
};

Signal.prototype.toString = function () {
	return this.value + "";
};

Signal.prototype.toJSON = function () {
	return this.value;
};

Signal.prototype.peek = function () {
	const prevContext = evalContext;
	evalContext = undefined;
	try {
		return this.value;
	} finally {
		evalContext = prevContext;
	}
};

Object.defineProperty(Signal.prototype, "value", {
	get(this: Signal) {
		const node = addDependency(this);
		if (node !== undefined) {
			node._version = this._version;
		}
		return this._value;
	},
	set(this: Signal, value) {
		if (value !== this._value) {
			if (batchIteration > 100) {
				throw new Error("Cycle detected");
			}

			this._value = value;
			this._version++;
			globalVersion++;

			/**@__INLINE__*/ startBatch();
			try {
				for (
					let node = this._targets;
					node !== undefined;
					node = node._nextTarget
				) {
					node._target._notify();
				}
			} finally {
				endBatch();
			}
		}
	},
});

/**
 * 创建一个新的普通signal。
 *
 * @param value signal的初始值。
 * @returns 一个新的signal。
 */
export function signal<T>(value: T, options?: SignalOptions<T>): Signal<T>;
export function signal<T = undefined>(): Signal<T | undefined>;
export function signal<T>(value?: T, options?: SignalOptions<T>): Signal<T> {
	return new Signal(value, options);
}

function needsToRecompute(target: Computed | Effect): boolean {
	// 检查依赖项的值是否有变化。依赖项列表已经按使用顺序排列。
	// 因此，如果多个依赖项的值发生了变化，此时只会重新评估
	// 第一个使用的依赖项。
	for (
		let node = target._sources;
		node !== undefined;
		node = node._nextSource
	) {
		if (
			// 如果依赖项自其版本号被观察到以来肯定已更新，
			// 那么我们需要重新计算。这第一个检查对于正确性来说并非严格必要，
			// 但如果依赖项已经更新，它允许我们跳过刷新调用。
			node._source._version !== node._version ||
			// 刷新依赖项。如果有什么阻止刷新的情况（例如依赖循环），
			// 那么我们需要重新计算。
			!node._source._refresh() ||
			// 如果依赖项在刷新后获得了新版本，那么我们需要重新计算。
			node._source._version !== node._version
		) {
			return true;
		}
	}
	// 如果自上次重新计算以来，没有任何依赖项的值发生变化，
	// 那么就不需要重新计算。
	return false;
}

function prepareSources(target: Computed | Effect) {
	/**
	 * 1. 将所有当前sources标记为可重用节点（version: -1）
	 * 2. 如果当前节点在不同的上下文中使用，设置回滚节点
	 * 3. 将'target._sources'指向双向链表的尾部，例如：
	 *
	 *    { undefined <- A <-> B <-> C -> undefined }
	 *                   ↑           ↑
	 *                   │           └──────┐
	 * target._sources = A; (节点是头部)    │
	 *                   ↓                  │
	 * target._sources = C; (节点是尾部) ───┘
	 */
	for (
		let node = target._sources;
		node !== undefined;
		node = node._nextSource
	) {
		const rollbackNode = node._source._node;
		if (rollbackNode !== undefined) {
			node._rollbackNode = rollbackNode;
		}
		node._source._node = node;
		node._version = -1;

		if (node._nextSource === undefined) {
			target._sources = node;
			break;
		}
	}
}

function cleanupSources(target: Computed | Effect) {
	let node = target._sources;
	let head: Node | undefined = undefined;

	/**
	 * 此时'target._sources'指向双向链表的尾部。
	 * 它包含所有现有sources + 按使用顺序排列的新sources。
	 * 向后迭代直到找到头节点，同时丢弃旧的依赖项。
	 */
	while (node !== undefined) {
		const prev = node._prevSource;

		/**
		 * 该节点未被重用，取消订阅其变更通知并将自身
		 * 从双向链表中移除。例如：
		 *
		 * { A <-> B <-> C }
		 *         ↓
		 *    { A <-> C }
		 */
		if (node._version === -1) {
			node._source._unsubscribe(node);

			if (prev !== undefined) {
				prev._nextSource = node._nextSource;
			}
			if (node._nextSource !== undefined) {
				node._nextSource._prevSource = prev;
			}
		} else {
			/**
			 * 新的头部是最后一个未从双向链表中
			 * 移除/取消订阅的节点。例如：
			 *
			 * { A <-> B <-> C }
			 *   ↑     ↑     ↑
			 *   │     │     └ head = node
			 *   │     └ head = node
			 *   └ head = node
			 */
			head = node;
		}

		node._source._node = node._rollbackNode;
		if (node._rollbackNode !== undefined) {
			node._rollbackNode = undefined;
		}

		node = prev;
	}

	target._sources = head;
}

/** @internal */
declare class Computed<T = any> extends Signal<T> {
	_fn: () => T;
	_sources?: Node;
	_globalVersion: number;
	_flags: number;

	constructor(fn: () => T, options?: SignalOptions<T>);

	_notify(): void;
	get value(): T;
}

/** @internal */
function Computed(this: Computed, fn: () => unknown, options?: SignalOptions) {
	Signal.call(this, undefined);

	this._fn = fn;
	this._sources = undefined;
	this._globalVersion = globalVersion - 1;
	this._flags = OUTDATED;
	this._watched = options?.watched;
	this._unwatched = options?.unwatched;
	this.name = options?.name;
}

Computed.prototype = new Signal() as Computed;

Computed.prototype._refresh = function () {
	this._flags &= ~NOTIFIED;

	if (this._flags & RUNNING) {
		return false;
	}

	// 如果这个computed signal已订阅了其依赖项的更新
	// （设置了TRACKING标志）并且它们都没有通知有关变化的信息（未设置OUTDATED
	// 标志），那么computed值不可能已经改变。
	if ((this._flags & (OUTDATED | TRACKING)) === TRACKING) {
		return true;
	}
	this._flags &= ~OUTDATED;

	if (this._globalVersion === globalVersion) {
		return true;
	}
	this._globalVersion = globalVersion;

	// 在检查依赖项的值变化之前，将这个computed signal标记为正在运行，
	// 这样RUNNING标志可以用来发现循环依赖。
	this._flags |= RUNNING;
	if (this._version > 0 && !needsToRecompute(this)) {
		this._flags &= ~RUNNING;
		return true;
	}

	const prevContext = evalContext;
	try {
		prepareSources(this);
		evalContext = this;
		const value = this._fn();
		if (
			this._flags & HAS_ERROR ||
			this._value !== value ||
			this._version === 0
		) {
			this._value = value;
			this._flags &= ~HAS_ERROR;
			this._version++;
		}
	} catch (err) {
		this._value = err;
		this._flags |= HAS_ERROR;
		this._version++;
	}
	evalContext = prevContext;
	cleanupSources(this);
	this._flags &= ~RUNNING;
	return true;
};

Computed.prototype._subscribe = function (node) {
	if (this._targets === undefined) {
		this._flags |= OUTDATED | TRACKING;

		// 当computed signal获得第一个订阅者时，
		// 它会惰性地订阅其依赖项。
		for (
			let node = this._sources;
			node !== undefined;
			node = node._nextSource
		) {
			node._source._subscribe(node);
		}
	}
	Signal.prototype._subscribe.call(this, node);
};

Computed.prototype._unsubscribe = function (node) {
	// 只有当computed signal有任何订阅者时才执行取消订阅步骤。
	if (this._targets !== undefined) {
		Signal.prototype._unsubscribe.call(this, node);

		// 当computed signal失去其最后一个订阅者时，它会取消订阅其依赖项。
		// 这使得未引用的computed signals子图可以被垃圾回收。
		if (this._targets === undefined) {
			this._flags &= ~TRACKING;

			for (
				let node = this._sources;
				node !== undefined;
				node = node._nextSource
			) {
				node._source._unsubscribe(node);
			}
		}
	}
};

Computed.prototype._notify = function () {
	if (!(this._flags & NOTIFIED)) {
		this._flags |= OUTDATED | NOTIFIED;

		for (
			let node = this._targets;
			node !== undefined;
			node = node._nextTarget
		) {
			node._target._notify();
		}
	}
};

Object.defineProperty(Computed.prototype, "value", {
	get(this: Computed) {
		if (this._flags & RUNNING) {
			throw new Error("Cycle detected");
		}
		const node = addDependency(this);
		this._refresh();
		if (node !== undefined) {
			node._version = this._version;
		}
		if (this._flags & HAS_ERROR) {
			throw this._value;
		}
		return this._value;
	},
});

/**
 * 只读signals的接口。
 */
interface ReadonlySignal<T = any> {
	readonly value: T;
	peek(): T;

	subscribe(fn: (value: T) => void): () => void;
	valueOf(): T;
	toString(): string;
	toJSON(): T;
	brand: typeof BRAND_SYMBOL;
}

/**
 * 创建一个基于其他signals值计算的新signal。
 *
 * 返回的computed signal是只读的，当回调函数中访问的任何signals
 * 发生变化时，其值会自动更新。
 *
 * @param fn effect回调。
 * @returns 一个新的只读signal。
 */
function computed<T>(
	fn: () => T,
	options?: SignalOptions<T>
): ReadonlySignal<T> {
	return new Computed(fn, options);
}

function cleanupEffect(effect: Effect) {
	const cleanup = effect._cleanup;
	effect._cleanup = undefined;

	if (typeof cleanup === "function") {
		/*@__INLINE__**/ startBatch();

		// 始终在任何上下文之外运行清理函数。
		const prevContext = evalContext;
		evalContext = undefined;
		try {
			cleanup();
		} catch (err) {
			effect._flags &= ~RUNNING;
			effect._flags |= DISPOSED;
			disposeEffect(effect);
			throw err;
		} finally {
			evalContext = prevContext;
			endBatch();
		}
	}
}

function disposeEffect(effect: Effect) {
	for (
		let node = effect._sources;
		node !== undefined;
		node = node._nextSource
	) {
		node._source._unsubscribe(node);
	}
	effect._fn = undefined;
	effect._sources = undefined;

	cleanupEffect(effect);
}

function endEffect(this: Effect, prevContext?: Computed | Effect) {
	if (evalContext !== this) {
		throw new Error("Out-of-order effect");
	}
	cleanupSources(this);
	evalContext = prevContext;

	this._flags &= ~RUNNING;
	if (this._flags & DISPOSED) {
		disposeEffect(this);
	}
	endBatch();
}

type EffectFn =
	| ((this: { dispose: () => void }) => void | (() => void))
	| (() => void | (() => void));

/** @internal */
declare class Effect {
	_fn?: EffectFn;
	_cleanup?: () => void;
	_sources?: Node;
	_nextBatchedEffect?: Effect;
	_flags: number;
	name?: string;

	constructor(fn: EffectFn, options?: EffectOptions);

	_callback(): void;
	_start(): () => void;
	_notify(): void;
	_dispose(): void;
	dispose(): void;
}

export interface EffectOptions {
	name?: string;
}

/** @internal */
function Effect(this: Effect, fn: EffectFn, options?: EffectOptions) {
	this._fn = fn;
	this._cleanup = undefined;
	this._sources = undefined;
	this._nextBatchedEffect = undefined;
	this._flags = TRACKING;
	this.name = options?.name;
}

Effect.prototype._callback = function () {
	const finish = this._start();
	try {
		if (this._flags & DISPOSED) return;
		if (this._fn === undefined) return;

		const cleanup = this._fn();
		if (typeof cleanup === "function") {
			this._cleanup = cleanup;
		}
	} finally {
		finish();
	}
};

Effect.prototype._start = function () {
	if (this._flags & RUNNING) {
		throw new Error("Cycle detected");
	}
	this._flags |= RUNNING;
	this._flags &= ~DISPOSED;
	cleanupEffect(this);
	prepareSources(this);

	/*@__INLINE__**/ startBatch();
	const prevContext = evalContext;
	evalContext = this;
	return endEffect.bind(this, prevContext);
};

Effect.prototype._notify = function () {
	if (!(this._flags & NOTIFIED)) {
		this._flags |= NOTIFIED;
		this._nextBatchedEffect = batchedEffect;
		batchedEffect = this;
	}
};

Effect.prototype._dispose = function () {
	this._flags |= DISPOSED;

	if (!(this._flags & RUNNING)) {
		disposeEffect(this);
	}
};

Effect.prototype.dispose = function () {
	this._dispose();
};
/**
 * 创建一个effect，以响应signal变化运行任意代码。
 *
 * effect会跟踪在给定回调函数`fn`中访问的signals，
 * 并在这些signals变化时重新运行回调。
 *
 * 回调可能返回一个清理函数。清理函数会被执行一次，
 * 要么在下次调用回调时，要么在effect被销毁时，
 * 以先发生者为准。
 *
 * @param fn effect回调。
 * @returns 用于销毁effect的函数。
 */
function effect(fn: EffectFn, options?: EffectOptions): () => void {
	const effect = new Effect(fn, options);
	try {
		effect._callback();
	} catch (err) {
		effect._dispose();
		throw err;
	}
	// 返回一个绑定函数而不是像`() => effect._dispose()`这样的包装器，
	// 因为绑定函数似乎同样快速且占用更少的内存。
	const dispose = effect._dispose.bind(effect);
	// @ts-ignore
	(dispose as any)[Symbol.dispose] = dispose;
	return dispose as any;
}

export {
	computed,
	effect,
	batch,
	untracked,
	Signal,
	// @ts-ignore
	ReadonlySignal,
	Effect,
	Computed,
};
